﻿using System;
using System.Collections.Generic;
using System.Linq;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.IoC;
using CoreEx.DynamicInvoker;
namespace CoreEx.MicroMapper
{
    public class MicroMapper<TSource, TTarget> : IMicroMapper<TSource, TTarget>, IInitialize
    {
        
        private IList<IAccessorInfoMatch> _accessorMatches;

        private readonly IList<Action<TSource, TTarget>> _explicitMappings
            = new List<Action<TSource, TTarget>>();

        public virtual TTarget Apply(TSource source)
        {                        
            TTarget target = TargetFactory(source);

            if (SourceAccessorSelector != null && TargetAccessorSelector != null)
            {

                if (_accessorMatches == null)
                    _accessorMatches =
                        AccessorInfoMatcher.Match(SourceAccessorSelector(source), TargetAccessorSelector(target)).ToList
                            ();


                


                if (SourceAccessor != null && TargetAccessor != null)
                {
                    foreach (IAccessorInfoMatch accessorInfoMatch in _accessorMatches)
                    {
                        object value =
                            ValueConverter.Convert(SourceAccessor(accessorInfoMatch.SourceAccessorInfo, source),
                                                   accessorInfoMatch.SourceAccessorInfo.DataType,
                                                   accessorInfoMatch.TargetAccessorInfo.DataType);
                        TargetAccessor(accessorInfoMatch.TargetAccessorInfo, target, value);
                    }
                }
            }
            foreach (var mapping in _explicitMappings)
                mapping(source, target);

            if (TargetPostProcessor != null)
                TargetPostProcessor(target);

            return target;
        }

        public virtual IEnumerable<TTarget> Apply(IEnumerable<TSource> sourceList)
        {
                return new List<TTarget>(sourceList.Select(s => Apply(s)));
        }

        public Func<IAccessorInfo, TSource, object> SourceAccessor { get; set; }

        public Action<IAccessorInfo, TTarget, object> TargetAccessor { get; set; }

        public Func<TSource,TTarget> TargetFactory { get; set; }

        public IMicroMapper<TSource, TTarget> Explicit(Action<TSource, TTarget> mapping)
        {
            _explicitMappings.Add(mapping);
            return this;
        }

        public Func<TSource, IEnumerable<IAccessorInfo>> SourceAccessorSelector { get; set; }

        public Func<TTarget, IEnumerable<IAccessorInfo>> TargetAccessorSelector { get; set; }

        public IAccessorInfoMatcher AccessorInfoMatcher { get; set; }
        public IValueConverter ValueConverter { get; set; }



        public void Reset()
        {
            if (_accessorMatches != null)
            {
                _accessorMatches.Clear();
                _accessorMatches = null;
            }
            _explicitMappings.Clear();
        }

        public IEnumerable<IAccessorInfoMatch> AccessorMatches
        {
            get { return _accessorMatches; }
        }

        public Action<TTarget> TargetPostProcessor { get; set;}
        
        #region IInitialize<IServiceContainer> Members

        public virtual void Initialize(IServiceContainer source)
        {
            
            SourceAccessorSelector = (s) =>
                                     typeof (TSource).GetProperties().Select(
                                         p =>
                                         (IAccessorInfo) new AccessorInfo {Name = p.Name, DataType = p.PropertyType});

            TargetAccessorSelector = (t) =>
                                     typeof (TTarget).GetProperties().Select(
                                         p =>
                                         (IAccessorInfo) new AccessorInfo {Name = p.Name, DataType = p.PropertyType});

            

            
            SourceAccessor = (accessorInfo, instance) => instance.GetType().DynamicInvoke(instance,accessorInfo.Name);

            
            TargetAccessor = (accessorInfo, instance,value) => instance.GetType().DynamicInvoke(instance,accessorInfo.Name,value);
            if (typeof(TTarget).IsInterface)
                TargetFactory = (s) => source.GetService<TTarget>();
            else
                TargetFactory = (s) => (TTarget) source.AutoCreate(typeof (TTarget));
            AccessorInfoMatcher = source.GetService<IAccessorInfoMatcher>();
            ValueConverter = source.GetService<IValueConverter>();
        }

        #endregion
    }
}
